package cn.stylefeng.guns.rabbitmq.util;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;

public final class DouDes3Util {

    private static final String CHAR_ENCODING = "UTF-8";
    private static final int LOOKUP_LENGTH = 64;
    private static final int EIGHT_BIT = 8;
    private static final int SIXTEEN_BIT = 16;
    private static final int TWENTY_FOUR_BIT_GROUP = 24;
    private static final int SIGN = -128;
    private static final byte PAD = (byte) '=';
    private static final byte[] lookUpBase64Alphabet = new byte[LOOKUP_LENGTH];
    static {
        for (int i = 0; i <= 25; i++) {
            lookUpBase64Alphabet[i] = (byte) ('A' + i);
        }

        for (int i = 26, j = 0; i <= 51; i++, j++) {
            lookUpBase64Alphabet[i] = (byte) ('a' + j);
        }

        for (int i = 52, j = 0; i <= 61; i++, j++) {
            lookUpBase64Alphabet[i] = (byte) ('0' + j);
        }

        lookUpBase64Alphabet[62] = (byte) '+';
        lookUpBase64Alphabet[63] = (byte) '/';
    }

    public static void main(String[] args) {
        try {
            String plainText = "1234556";
            String userKey = "6Gt5J8nY";
            String encode = encode(userKey, plainText);
            System.out.println(encode);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 入口工具方法
     * @param key 加密密钥
     * @param data 加密明文
     * @return 加密密文
     */
    public static String encode(String key, String data) throws Exception {
        byte[] keyByte = key.getBytes(CHAR_ENCODING);
        byte[] dataByte = data.getBytes(CHAR_ENCODING);
        byte[] valueByte = des3Encryption(keyTo24Byte(keyByte), dataByte);
        return new String(encode(valueByte), CHAR_ENCODING);
    }

    //底层实现
    private static byte[] des3Encryption(byte[] key, byte[] data) throws
            NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException,
            BadPaddingException, IllegalBlockSizeException {
        final String Algorithm = "DESede";
        if (key.length != 24) {
            throw new RuntimeException("Invalid DESede key length (must be 24 bytes)");
        }
        Key desKey = new SecretKeySpec(key, Algorithm);
        Cipher c1 = Cipher.getInstance(Algorithm);
        c1.init(Cipher.ENCRYPT_MODE, desKey);
        return c1.doFinal(data);
    }

    //辅助工具方法
    private static byte[] keyTo24Byte(byte[] keyByte) {
        if (keyByte == null || keyByte.length == 0) {
            throw new RuntimeException("keyByte is null");
        }
        byte[] toByte;
        if (keyByte.length == 24) {
            return keyByte;
        } else if (keyByte.length > 24) {
            toByte = new byte[24];
            System.arraycopy(keyByte, 0, toByte, 0, 24);
        } else {
            toByte = new byte[24];
            System.arraycopy(keyByte, 0, toByte, 0, keyByte.length);
        }
        return toByte;
    }

    private static byte[] encode(byte[] pArray) {
        return encodeBase64(pArray);
    }

    private static byte[] encodeBase64(byte[] binaryData) {
        int lengthDataBits = binaryData.length * EIGHT_BIT;
        int fewerThan24bits = lengthDataBits % TWENTY_FOUR_BIT_GROUP;
        int numberTriplets = lengthDataBits / TWENTY_FOUR_BIT_GROUP;
        byte encodedData[];
        int encodedDataLength;

        encodedDataLength = fewerThan24bits == 0 ? numberTriplets << 2 : (numberTriplets + 1) << 2;
        encodedData = new byte[encodedDataLength];
        byte k, l, b1, b2;
        int encodedIndex = 0;
        int dataIndex;
        int i;

        for (i = 0; i < numberTriplets; i++) {
            dataIndex = i * 3;
            b1 = binaryData[dataIndex];
            b2 = binaryData[dataIndex + 1];
            byte b3 = binaryData[dataIndex + 2];

            l = (byte) (b2 & 0x0f);
            k = (byte) (b1 & 0x03);

            byte val1 =
                    ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
            byte val2 =
                    ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
            byte val3 =
                    ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc);

            encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
            encodedData[encodedIndex + 1] =
                    lookUpBase64Alphabet[val2 | (k << 4)];
            encodedData[encodedIndex + 2] =
                    lookUpBase64Alphabet[(l << 2) | val3];
            encodedData[encodedIndex + 3] = lookUpBase64Alphabet[b3 & 0x3f];

            encodedIndex += 4;
        }
        dataIndex = i * 3;

        if (fewerThan24bits == EIGHT_BIT) {
            b1 = binaryData[dataIndex];
            k = (byte) (b1 & 0x03);
            byte val1 =
                    ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
            encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
            encodedData[encodedIndex + 1] = lookUpBase64Alphabet[k << 4];
            encodedData[encodedIndex + 2] = PAD;
            encodedData[encodedIndex + 3] = PAD;
        } else if (fewerThan24bits == SIXTEEN_BIT) {
            b1 = binaryData[dataIndex];
            b2 = binaryData[dataIndex + 1];
            l = (byte) (b2 & 0x0f);
            k = (byte) (b1 & 0x03);
            byte val1 =
                    ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
            byte val2 =
                    ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);

            encodedData[encodedIndex] = lookUpBase64Alphabet[val1];
            encodedData[encodedIndex + 1] =
                    lookUpBase64Alphabet[val2 | (k << 4)];
            encodedData[encodedIndex + 2] = lookUpBase64Alphabet[l << 2];
            encodedData[encodedIndex + 3] = PAD;
        }
        return encodedData;
    }
}
